package com.atguigu.daijia.coupon.service.impl;

import com.atguigu.daijia.common.constant.RedisConstant;
import com.atguigu.daijia.common.execption.GuiguException;
import com.atguigu.daijia.common.result.ResultCodeEnum;
import com.atguigu.daijia.coupon.mapper.CouponInfoMapper;
import com.atguigu.daijia.coupon.mapper.CustomerCouponMapper;
import com.atguigu.daijia.coupon.service.CouponInfoService;
import com.atguigu.daijia.model.entity.coupon.CouponInfo;
import com.atguigu.daijia.model.entity.coupon.CustomerCoupon;
import com.atguigu.daijia.model.form.coupon.UseCouponForm;
import com.atguigu.daijia.model.vo.base.PageVo;
import com.atguigu.daijia.model.vo.coupon.AvailableCouponVo;
import com.atguigu.daijia.model.vo.coupon.NoReceiveCouponVo;
import com.atguigu.daijia.model.vo.coupon.NoUseCouponVo;
import com.atguigu.daijia.model.vo.coupon.UsedCouponVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@SuppressWarnings({"unchecked", "rawtypes"})
@RequiredArgsConstructor
public class CouponInfoServiceImpl extends ServiceImpl<CouponInfoMapper, CouponInfo> implements CouponInfoService {

    private final CouponInfoMapper couponInfoMapper;
    private final CustomerCouponMapper customerCouponMapper;
    private final RedissonClient redissonClient;

    /**
     * 分页查询乘客未领取的优惠券列表
     * @param pageParam
     * @param customerId
     * @return
     */
    @Override
    public PageVo<NoReceiveCouponVo> findNoReceiveCouponPage(IPage<CouponInfo> pageParam, Long customerId) {
        IPage<NoReceiveCouponVo> page = couponInfoMapper.findNoReceiveCouponPage(pageParam, customerId);
        return new PageVo<>(page.getRecords(), page.getPages(), page.getTotal());
    }

    /**
     * 分页查询乘客未使用的优惠券列表
     * @param pageParam
     * @param customerId
     * @return
     */
    @Override
    public PageVo<NoUseCouponVo> findNoUseCouponPage(IPage<CouponInfo> pageParam, Long customerId) {
        IPage<NoUseCouponVo> page = couponInfoMapper.findNoUseCouponPage(pageParam, customerId);
        return new PageVo<>(page.getRecords(), page.getPages(), page.getTotal());
    }

    /**
     * 分页查询乘客已使用的优惠券列表
     * @param pageParam
     * @param customerId
     * @return
     */
    @Override
    public PageVo<UsedCouponVo> findUsedCouponPage(IPage<CouponInfo> pageParam, Long customerId) {
        IPage<UsedCouponVo> page = couponInfoMapper.findUsedCouponPage(pageParam, customerId);
        return new PageVo<>(page.getRecords(), page.getPages(), page.getTotal());
    }

    /**
     * 领取优惠券
     * @param customerId
     * @param couponId
     * @return
     */
    @Override
    @Transactional
    public Boolean receiveCoupon(Long customerId, Long couponId) {
        // 查询优惠券信息
        CouponInfo couponInfo = this.getById(couponId);
        if (couponInfo == null) {
            throw new GuiguException(ResultCodeEnum.DATA_ERROR);
        }

        // 判断优惠券是否过期
        if (couponInfo.getExpireTime().before(new Date())) {
            throw new GuiguException(ResultCodeEnum.COUPON_EXPIRE);
        }

        // 判断优惠券库存
        if (couponInfo.getPublishCount() != 0 && couponInfo.getReceiveCount() >= couponInfo.getPublishCount()) {
            throw new GuiguException(ResultCodeEnum.COUPON_LESS);
        }

        RLock lock = null;
        try {
            // 创建分布式锁，锁的 key 为 customerId，将锁的粒度降到最低，减少锁冲突
            lock = redissonClient.getLock(RedisConstant.COUPON_LOCK + customerId);
            // 尝试获取分布式锁，尝试 1 秒，如果获取锁成功，不设置锁的过期时间，Redisson 会默认将锁的过期时间设置为 30 秒，并启动看门狗（watchdog）机制在业务执行期间自动给锁续期，避免业务执行时间过长，锁自动释放
            boolean flag = lock.tryLock(RedisConstant.COUPON_LOCK_WAIT_TIME, TimeUnit.SECONDS);
            if (flag) {
                // 判断当前乘客领取的当前优惠券数量是否超过限制
                if (couponInfo.getPerLimit() > 0) {
                    Long count = customerCouponMapper.selectCount(new LambdaQueryWrapper<CustomerCoupon>().eq(CustomerCoupon::getCouponId, couponId)
                            .eq(CustomerCoupon::getCustomerId, customerId));
                    if (count >= couponInfo.getPerLimit()) {
                        throw new GuiguException(ResultCodeEnum.COUPON_USER_LIMIT);
                    }
                }

                // 领取优惠券，更新优惠券领取数量，保存领取记录到 customer_coupon 表中
                // UPDATE coupon_info SET receive_count = receive_count + 1 WHERE id = ? AND receive_count < 100
                boolean updated = this.lambdaUpdate().set(CouponInfo::getReceiveCount, couponInfo.getReceiveCount() + 1)
                        .eq(CouponInfo::getId, couponId)
                        // 乐观锁
                        .lt(CouponInfo::getReceiveCount, couponInfo.getPublishCount())
                        .update();
                if (!updated) {
                    throw new GuiguException(ResultCodeEnum.UPDATE_ERROR);
                } else {
                    // 保存领取记录到 customer_coupon 表中
                    CustomerCoupon customerCoupon = new CustomerCoupon();
                    customerCoupon.setCouponId(couponId);
                    customerCoupon.setCustomerId(customerId);
                    customerCoupon.setExpireTime(couponInfo.getExpireTime());
                    // 领取优惠券默认为未使用
                    customerCoupon.setStatus(1);
                    customerCoupon.setReceiveTime(new Date());
                    int i = customerCouponMapper.insert(customerCoupon);
                    if (i != 1) {
                        throw new GuiguException(ResultCodeEnum.UPDATE_ERROR);
                    }
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            throw new GuiguException(ResultCodeEnum.REDIS_ERROR);
        } finally {
            // 业务代码执行结束，手动释放锁，减少锁等待
            if (lock != null && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        return true;
    }

    /**
     * 查询可用优惠券列表并按照优惠力度降序排列
     * @param customerId
     * @param orderAmount
     * @return
     */
    @Override
    public List<AvailableCouponVo> findAvailableCouponList(Long customerId, BigDecimal orderAmount) {

        List<AvailableCouponVo> availableCouponVoList = new ArrayList<>();

        // 查询所有可用优惠券列表
        List<NoUseCouponVo> noUseCouponList = couponInfoMapper.findNoUseCouponList(customerId);

        // 所有可用的现金券列表
        List<NoUseCouponVo> noUseCashCouponList = noUseCouponList.stream().filter(coupon -> coupon.getCouponType() == 1).collect(Collectors.toList());

        // 将符合条件的现金券添加到 availableCouponVoList
        noUseCashCouponList.forEach(item -> {
            BigDecimal reduceAmount = item.getAmount();
            if (item.getConditionAmount().doubleValue() == 0 && orderAmount.subtract(reduceAmount).doubleValue() >= 0) {
                // 没有使用门槛的现金券，而且订单金额大于等于现金券减免金额，是可用优惠券
                availableCouponVoList.add(this.buildAvailableCouponVo(item, reduceAmount));
            }
            if (item.getConditionAmount().doubleValue() > 0 && orderAmount.subtract(item.getConditionAmount()).doubleValue() >= 0) {
                // 有使用门槛的现金券，而且订单金额大于等于现金券使用门槛，是可用优惠券
                availableCouponVoList.add(this.buildAvailableCouponVo(item, reduceAmount));
            }
        });

        // 所有可用的折扣券列表
        List<NoUseCouponVo> noUseDiscountCouponList = noUseCouponList.stream().filter(coupon -> coupon.getCouponType() == 2).collect(Collectors.toList());

        // 将符合条件的折扣券添加到 availableCouponVoList
        noUseDiscountCouponList.forEach(item -> {
            // 使用折扣券后的订单金额
            BigDecimal discountOrderAmount = orderAmount.multiply(item.getDiscount()).divide(new BigDecimal("10")).setScale(2, RoundingMode.HALF_UP);
            // 使用折扣券后的减免金额
            BigDecimal reduceAmount = orderAmount.subtract(discountOrderAmount);
            if (item.getConditionAmount().doubleValue() == 0) {
                // 没有使用门槛的折扣券，都是可用优惠券
                availableCouponVoList.add(this.buildAvailableCouponVo(item, reduceAmount));
            } else if (orderAmount.subtract(item.getConditionAmount()).doubleValue() >= 0) {
                // 有使用门槛的折扣券，如果订单金额大于等于折扣券使用门槛，是可用优惠券
                availableCouponVoList.add(this.buildAvailableCouponVo(item, reduceAmount));
            }
        });

        // 按照优惠券的优惠力度降序排列
        if (!CollectionUtils.isEmpty(availableCouponVoList)) {
            availableCouponVoList.sort(Comparator.comparing(AvailableCouponVo::getReduceAmount).reversed());
        }

        return availableCouponVoList;
    }

    private AvailableCouponVo buildAvailableCouponVo(NoUseCouponVo noUseCouponVo, BigDecimal reduceAmount) {
        AvailableCouponVo availableCouponVo = new AvailableCouponVo();
        BeanUtils.copyProperties(noUseCouponVo, availableCouponVo);
        availableCouponVo.setCouponId(noUseCouponVo.getId());
        availableCouponVo.setReduceAmount(reduceAmount);
        return availableCouponVo;
    }

    /**
     * 使用优惠券
     * @param useCouponForm
     * @return
     */
    @Override
    @Transactional
    public BigDecimal useCoupon(UseCouponForm useCouponForm) {
        // 查询乘客与优惠券关联关系
        CustomerCoupon customerCoupon = customerCouponMapper.selectById(useCouponForm.getCustomerCouponId());
        if (customerCoupon == null) {
            throw new GuiguException(ResultCodeEnum.ARGUMENT_VALID_ERROR);
        }

        // 获取优惠券信息
        CouponInfo couponInfo = this.getById(customerCoupon.getCouponId());
        if (couponInfo == null) {
            throw new GuiguException(ResultCodeEnum.ARGUMENT_VALID_ERROR);
        }

        // 判断优惠券是否为当前乘客所有
        if (!customerCoupon.getCustomerId().equals(useCouponForm.getCustomerId())) {
            throw new GuiguException(ResultCodeEnum.ILLEGAL_REQUEST);
        }

        // 获取优惠券的减免金额
        BigDecimal reduceAmount = null;
        if (couponInfo.getCouponType() == 1) {
            // 乘客使用了现金券
            if (couponInfo.getConditionAmount().doubleValue() == 0 && useCouponForm.getOrderAmount().subtract(couponInfo.getAmount()).doubleValue() >= 0) {
                // 没有使用门槛的现金券，而且订单金额大于等于现金券减免金额，是可用优惠券，获取减免金额
                reduceAmount = couponInfo.getAmount();
            }
            if (couponInfo.getConditionAmount().doubleValue() > 0 && useCouponForm.getOrderAmount().subtract(couponInfo.getConditionAmount()).doubleValue() >= 0) {
                // 有使用门槛的现金券，而且订单金额大于等于现金券使用门槛，是可用优惠券，获取减免金额
                reduceAmount = couponInfo.getAmount();
            }
        } else {
            // 乘客使用了折扣券
            // 使用折扣券后的订单金额
            BigDecimal discountOrderAmount = useCouponForm.getOrderAmount().multiply(couponInfo.getDiscount()).divide(new BigDecimal("10")).setScale(2, RoundingMode.HALF_UP);
            if (couponInfo.getConditionAmount().doubleValue() == 0) {
                // 没有使用门槛的折扣券，都是可用优惠券，获取减免金额
                reduceAmount = useCouponForm.getOrderAmount().subtract(discountOrderAmount);
            } else if (discountOrderAmount.subtract(couponInfo.getConditionAmount()).doubleValue() >= 0) {
                // 有使用门槛的折扣券，如果订单金额大于等于折扣券使用门槛，是可用优惠券，获取减免金额
                reduceAmount = useCouponForm.getOrderAmount().subtract(discountOrderAmount);
            }
        }

        if (BigDecimal.ZERO.compareTo(reduceAmount) < 0) {
            // 优惠券减免金额大于 0，乘客会使用该优惠券
            // 更新优惠券使用数量
            boolean updated = this.lambdaUpdate()
                    .set(CouponInfo::getUseCount, couponInfo.getUseCount() + 1)
                    .eq(CouponInfo::getId, couponInfo.getId())
                    .update();
            if (!updated) {
                throw new GuiguException(ResultCodeEnum.UPDATE_ERROR);
            }
            // 更新乘客优惠券状态为已使用，使用时间为当前时间
            customerCoupon.setStatus(2);
            customerCoupon.setUsedTime(new Date());
            int i = customerCouponMapper.updateById(customerCoupon);
            if (i == 0) {
                throw new GuiguException(ResultCodeEnum.UPDATE_ERROR);
            }
        }
        return reduceAmount;
    }
}
